#include <g-bios.h>
#include <flash/flash.h>
#include <flash/part.h>
#include <arch/mem.h>
#include <core/cmd.h>
#include <core/getopt.h>
#include <core/uart.h>


struct CmdStack
{
	char *vpchCmdStack[APP_HST_DEP];
	int	  iCmdHstSP;
};

static struct CmdStack *g_pCmdStack = NULL;


static __INLINE__ void CmdBackspace(void)
{
	printf("\033[D\033[1P");
}


static void ShowPrompt(void)
{	
	int d = 1;
	struct Partition *pPart;


	pPart = GuPartOpenCur();
	if (NULL != pPart)
	{
		d = GuPartGetIndex(pPart);
		GuPartClose(pPart);
	}

	printf("g-bios: %d# ", d);
}


static void InsertOneKey(char input_c, char *buf, int *cur_pos, int *cur_max)
{
	int i;

	for (i = *cur_max + 1; i > *cur_pos; --i)
		buf[i] = buf[i - 1];
	buf[i] = input_c;
	++(*cur_pos);
	++(*cur_max);

	printf("\033[4h");
	putchar(input_c);
	printf("\033[4l");
}


static void BackSpaceOneKey(char *buf, int *cur_pos, int *cur_max)
{
	int i;

	for (i = *cur_pos; i < *cur_max; ++i)
		buf[i - 1] = buf[i];
	buf[*cur_max] = '\0';
	--(*cur_max);
	--(*cur_pos);

	CmdBackspace();
}


static void DeleteOneKey(char *buf, int *cur_pos, int *cur_max)
{
	int i;

	if (*cur_pos == *cur_max)
		return;

	for (i = *cur_pos; i < *cur_max - 1; ++i)
		buf[i] = buf[i + 1];
	buf[*cur_max] = '\0';

	--(*cur_max);

	printf("\033[1P");
}


static void ShowInputBuf(char *buf, const int cur_pos, const int cur_max)
{
	int i;

	for (i = 0; i < cur_max; ++i)
		putchar(buf[i]);
	for (i = cur_max; i > cur_pos; --i)
		printf("\033[D");
}


static int CmdMatch(char *buf, int *cur_pos, int *cur_max)
{
	int	i = 0, j = 0, k, m, n;
	char (*pszResult)[MAX_ARGV_SIZE];
	char ch;
	GApp *pApp;
	UINT32 nLen;
	BOOL bFlag;

	nLen = ((UINT32)g_pGBiosAppEnd - (UINT32)g_pGBiosAppBegin) / sizeof(GApp);
	pszResult = malloc(MAX_ARGV_SIZE * nLen);
	if (NULL == pszResult)
	{
		DPRINT("ERROR: fail to malloc, %s,%d", __FUNCTION__, __LINE__);
		return -ENOMEM;
	}

	for (pApp = g_pGBiosAppBegin; pApp < g_pGBiosAppEnd; pApp++)
	{
		if (!*cur_pos || strncmp(pApp->pchName, buf, *cur_pos) == 0)
			strcpy(pszResult[j++], pApp->pchName);
	}

	switch (j)
	{
		case 0:
			break;

		case 1:
			i = strlen(pszResult[0]);

			for (; *cur_pos < i; )
			{
				InsertOneKey(pszResult[0][*cur_pos], buf, cur_pos, cur_max); 
			}
			if (*cur_pos == *cur_max)
			{
				InsertOneKey(' ', buf, cur_pos, cur_max);
			}

			break;
		
		default:
			for (i = *cur_pos; ch = pszResult[0][i]; *cur_pos = ++i)
			{
				bFlag = FALSE;
				for (k = 1; k < j; k++)
					if (ch != pszResult[k][i])
						bFlag = TRUE;

				if (bFlag) break;

				InsertOneKey(ch, buf, cur_pos, cur_max);
			}

			putchar('\n');
			for (i = 1; i < j + 1; i++)
			{
				printf("%-20s", pszResult[i - 1]);
				if (0 == (i & 0x3))
					putchar('\n');
			}
			if (0 != (j & 0x3))
				putchar('\n');

			ShowPrompt();
			ShowInputBuf(buf, *cur_pos, *cur_max);

			break;
	}

	free(pszResult);

	return 0;
}


static void PrintInput(char input_c, char *buf, int *cur_pos, int *cur_max)
{
	int i;

	if (*cur_pos < MAX_ARGV_SIZE - 1)
	{
		InsertOneKey(input_c, buf, cur_pos, cur_max);
	}
	else
	{
		putchar('\n');
		printf("error: command too long\nthe command must be less than %d letter", MAX_ARGV_SIZE);
		putchar('\n');
		ShowPrompt();
//		printf("\n%s", g_szPrompt);
		printf(buf);
	}
}


static int CmdUpKey(char *buf, int *cur_pos, int *pindex, int *cur_max)
{
	int i;
	int index = *pindex;
	int lpos = *cur_pos;

	//save current buffer, but history index does not increase
	if (index == g_pCmdStack->iCmdHstSP)
	{
		buf[lpos] = '\0';

		if (NULL == g_pCmdStack->vpchCmdStack[g_pCmdStack->iCmdHstSP])
		{
			if (NULL == (g_pCmdStack->vpchCmdStack[g_pCmdStack->iCmdHstSP] = (char *)malloc(MAX_ARGV_SIZE)))
			{
				printf("ERROR: fail to malloc!\n");
				return -1;
			}
		}
		strcpy(g_pCmdStack->vpchCmdStack[g_pCmdStack->iCmdHstSP], buf);
	}

	index = APP_ADJUST_INDEX(index - 1);

	if (index != g_pCmdStack->iCmdHstSP && NULL != g_pCmdStack->vpchCmdStack[index])
	{
		// erase the command on screen
		for (i = 0; i < lpos; i++)
		{
			buf[i] = '\0'; //erase input buffer
			CmdBackspace();  
		}

		// show history command
		printf("%s", g_pCmdStack->vpchCmdStack[index]);

		//update buffer & index
		strcpy(buf, g_pCmdStack->vpchCmdStack[index]);
		*pindex = index;
		*cur_pos = strlen(g_pCmdStack->vpchCmdStack[index]);
		*cur_max = *cur_pos;

		printf("\033[0K");

		return 0;
	}

	return -1;
}


static int CmdDownKey(char *buf, int *cur_pos, int *pindex, int *cur_max)
{
	int i;
	int index = *pindex;
	int lpos = *cur_pos;

	if (index == g_pCmdStack->iCmdHstSP)
		return 0;

	index = APP_ADJUST_INDEX(index + 1);

	// erase the command on screen
	for (i = 0; i < lpos; i++)
	{
		buf[i] = '\0'; //erase input buffer
		CmdBackspace();  
	}

	// show history command
	printf("%s", g_pCmdStack->vpchCmdStack[index]);

	//update buffer & index
	strcpy(buf, g_pCmdStack->vpchCmdStack[index]);
	*pindex = index;
	*cur_pos = strlen(g_pCmdStack->vpchCmdStack[index]);
	*cur_max = *cur_pos;

	printf("\033[0K");

	return 0;
}

static int CmdRightKey(char *buf, int *cur_pos, int *cur_max)
{
	if (*cur_pos < *cur_max)
	{
		++(*cur_pos);
		printf("\033[C");
	}

	return 0;
}


static int CmdLeftKey(char *buf, int *cur_pos, int *cur_max)
{
	if (*cur_pos > 0)
	{
		--(*cur_pos);
		printf("\033[D");
	}

	return 0;
}


static int CmdUpdateHistory(const char *buf)
{
	if (NULL == g_pCmdStack->vpchCmdStack[g_pCmdStack->iCmdHstSP])
	{
		if (NULL == (g_pCmdStack->vpchCmdStack[g_pCmdStack->iCmdHstSP] = (char *)malloc(MAX_ARGV_SIZE)))
		{
			printf("ERROR: fail to malloc!\n");
			return -1;
		}
	}

	strcpy(g_pCmdStack->vpchCmdStack[g_pCmdStack->iCmdHstSP], buf);

	g_pCmdStack->iCmdHstSP = APP_ADJUST_INDEX(++g_pCmdStack->iCmdHstSP);

	return 0;
}

int GetMessage(char *buf)
{
	char input_c;
	int cur_pos = 0;
	int cur_max = 0;
	int hst_pos = g_pCmdStack->iCmdHstSP;
	int esc_sequence = 0;
	int spec_key = 0;
	int ret;

	memset(buf, '\0', MAX_ARGV_SIZE);

	while (0 == cur_max)
	{
		input_c = 0;

		ShowPrompt();
		while (input_c != '\r' && input_c != '\n')
		{
			input_c = raw_getchar();
			//fixme: support F1 F2 F3 ...
			if (input_c == '\033')
			{
				spec_key = 1;
				input_c = raw_getchar();
				if (input_c == '[')
				{
					esc_sequence = 1;
					input_c = raw_getchar();
				}
			}
			else
			{
				spec_key = 0;
				esc_sequence = 0;
			}

			switch (input_c) 
			{
			case '\t':
				CmdMatch(buf, &cur_pos, &cur_max);

				break;

			case '\r':
			case '\n':
				putchar('\n');
				break;

			case 0x03: // ctrl + c
				cur_pos = 0;
				cur_max = 0;
				input_c = '\n';
				putchar(input_c);
				break;

			case 0x08:
			case 0x7f:
				if (cur_pos > 0) 
				{
					BackSpaceOneKey(buf, &cur_pos, &cur_max);
				}

				break;

			case 'A':
			case 'B':// no filter: escape +' [' + 'A'
				if (1 == esc_sequence)
				{
					if ('A' == input_c) 
						ret = CmdUpKey(buf, &cur_pos, &hst_pos, &cur_max);
					else
						ret = CmdDownKey(buf, &cur_pos, &hst_pos, &cur_max);
				}
				else // not a up/down key, treat it as a normal input
				{
					PrintInput(input_c, buf, &cur_pos, &cur_max);
				}

				break;

			case 'C':
			case 'D':
				if (1 == esc_sequence)
				{
					if ('C' == input_c)
						ret = CmdRightKey(buf, &cur_pos, &cur_max);
					else
						ret = CmdLeftKey(buf, &cur_pos, &cur_max);
					break;
				}
				else // not a left/right key, treat it as a normal input
				{
					PrintInput(input_c, buf, &cur_pos, &cur_max);
				}

				break;

			case 'O':
				if (1 == spec_key)
				{
					input_c = raw_getchar();

					if ('H' == input_c)
					{
						if (cur_pos != 0)
							printf("\033[%dD", cur_pos);
						cur_pos = 0;				
					}
					if ('F' == input_c)
					{
						if (cur_pos != cur_max)
							printf("\033[%dC", cur_max - cur_pos);
						cur_pos = cur_max;
					}
				}
				else // not a Home/End key, treat it as a normal input
				{
					PrintInput(input_c, buf, &cur_pos, &cur_max);
				}

				break;

			case '3':
				if (1 == spec_key)
				{
					input_c = raw_getchar();

					if ('~' == input_c)
					{
						DeleteOneKey(buf, &cur_pos, &cur_max);
					}
				}
				else
				{
					PrintInput(input_c, buf, &cur_pos, &cur_max);
				}

				break;

			default:
				if (input_c >= 0x20 && input_c <= 0x7f) //filter the character
					PrintInput(input_c, buf, &cur_pos, &cur_max);

				break;
			}
		}
	}

	buf[cur_max] = '\0';

	CmdUpdateHistory(buf);

	return 1;
}


int TranslateMessage(const char *buf, char *argv[])
{
	int num = 0;
	int i = 0, j = 0;
	char *args;

	args = (char *)malloc(MAX_ARGV_SIZE);
	if (NULL == args)
	{
		printf("\nERROR: malloc fail!\n");
		return -ENOMEM;
	}

	while (buf[j] && num < MAX_APP_ARG)
	{
		if (buf[j] == ' ')
		{
			j++;
		}
		else
		{
			argv[num] = args + i;

			while (buf[j] && buf[j] != ' ')
			{
				args[i] = buf[j];
				i++;
				j++;
			}

			args[i] = '\0';
			i++;
			num++;
		}
	}

	return num;
}


int DispatchMessage(int argc, char *argv[])
{
	GApp *pApp;
	int num, ret;


	for (pApp = g_pGBiosAppBegin; pApp < g_pGBiosAppEnd; pApp++)
	{
		if (!strncmp(pApp->pchName, argv[0], MAX_ARGV_SIZE))
		{
			getopt_init();

			ret = pApp->main(argc, argv);
#if 0
			if (ret < 0)
			{
				printf("fail to exec %s! (exit %d)\n", argv[0], ret);
			}
#endif
			goto L_exec;
		}
	}

	printf("  Command \"%s\" not found.\n  Please use \"help\" to get g-bios command list.\n", argv[0]);
	ret = -ENOEXEC;

L_exec:
	free(argv[0]);
	putchar('\n');

	return ret;
}


int __INIT__ CreatetCmdQueue(void)
{
	g_pCmdStack = (struct CmdStack *)zalloc(sizeof(*g_pCmdStack));

	if (NULL == g_pCmdStack)
	{
		printf("No memory: file: %s, %s(): line %d\n",
				__FILE__, __FUNCTION__, __LINE__);

		return -ENOMEM;
	}

	return 0;
}

